﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using FLUDP.Utility;

namespace FLUDP
{
    public class FludpServer
    {
        public IPAddress ipAddress;
        public int port;
        public bool started = false;

        private Socket serverSocket;
        private byte[] byteData = new byte[1024];
        public List<ClientInfo> clientList;

        public EventHandler eStart, eReceive, eStop;

        public bool StartServer(int port)
        {
            if (started)
                return false;

            return StartServer(FludpUtility.LocalIPAddress(), port);
        }
        public bool StartServer(string ipAddress, int port)
        {
            if (started)
                return false;

            if (IPAddress.TryParse(ipAddress, out this.ipAddress))
                return StartServer(this.ipAddress, port);
            else 
                return false;
        }
        public bool StartServer(IPAddress ipAddress, int port)
        {
            if (started)
                return false;

            //Sets the fields to the actual values
            this.ipAddress = ipAddress;
            this.port = port;

            try
            {
                //We are using UDP sockets
                serverSocket = new Socket(AddressFamily.InterNetwork,
                    SocketType.Dgram, ProtocolType.Udp);

                //Assign the any IP of the machine and listen on port number 1000
                IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);

                //Bind this address to the server
                serverSocket.Bind(ipEndPoint);

                IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0);
                //The epSender identifies the incoming clients
                EndPoint epSender = (EndPoint)ipeSender;

                //Start receiving data
                serverSocket.BeginReceiveFrom(byteData, 0, byteData.Length,
                    SocketFlags.None, ref epSender,
                       new AsyncCallback(OnReceive), epSender);

                started = true;
                clientList = new List<ClientInfo>();

                if(eStart != null) eStart(this, EventArgs.Empty);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public void StopServer()
        {
            serverSocket.Close();
            started = false;
            if (eStop != null) eStop(this, EventArgs.Empty);
        }

        public void OnSend(IAsyncResult ar)
        {
            try
            {
                serverSocket.EndSend(ar);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public void OnReceive(IAsyncResult ar) 
        {
            IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0);
            EndPoint epSender = (EndPoint)ipeSender;
            try
            {
                serverSocket.EndReceiveFrom(ar, ref epSender);

                //Transform the array of bytes received from the user into an
                //intelligent form of object Data
                Data msgReceived = new Data(byteData);

                //clientInfo can be null if there's no client with this endpoint
                //The program has to handle this case on its own

                if (eReceive != null) eReceive(this, new FludpReceiveEventArgs(msgReceived.cmdCommand, msgReceived.strName, msgReceived.strMessage, epSender));

                byteData = new byte[1024];
                serverSocket.BeginReceiveFrom(byteData, 0, byteData.Length, SocketFlags.None, ref epSender,
                    new AsyncCallback(OnReceive), epSender);
            }
            catch { }
        }

        public void SendDataToAllExcept(Command command, string name, string message)
        {
            Data data = new Data();
            data.cmdCommand = command;
            data.strName = name;
            data.strMessage = message;
            SendDataToAllExcept(data);
        }
        public void SendDataToAllExcept(Data data)
        {
            byte[] message = data.ToByte();

            foreach (ClientInfo ci in clientList)
                if(ci.name != data.strName)
                    SendData(ci.endpoint, data);
        }
        public void SendDataToAll(Command command, string name, string message)
        {
            Data data = new Data();
            data.cmdCommand = command;
            data.strName = name;
            data.strMessage = message;
            SendDataToAll(data);
        }
        public void SendDataToAll(Data data)
        {
            foreach (ClientInfo ci in clientList)
                SendData(ci.endpoint, data);
        }
        public void SendData(EndPoint epSender, Command command, string name, string message)
        {
            Data data = new Data();
            data.cmdCommand = command;
            data.strName = name;
            data.strMessage = message;
            SendData(epSender, data);
        }
        public void SendData(EndPoint epSender, Data data)
        {
            byte[] message = data.ToByte();

            serverSocket.BeginSendTo(message, 0, message.Length, SocketFlags.None, epSender,
                new AsyncCallback(OnSend), epSender);
        }

        public void RemoveClient(string name)
        {
            foreach (ClientInfo ci in clientList)
            {
                if (ci.name == name)
                {
                    clientList.Remove(ci);
                    return;
                }
            }
        }
    }
}
